2 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following
13 * disclaimer in the documentation and/or other materials
14 * provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
26 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include "RenderFlowThread.h"
34 #include "HitTestRequest.h"
35 #include "HitTestResult.h"
37 #include "PaintInfo.h"
38 #include "RenderLayer.h"
39 #include "RenderRegion.h"
40 #include "RenderView.h"
41 #include "TransformState.h"
45 RenderFlowThread::RenderFlowThread(Node* node, const AtomicString& flowThread)
47 , m_flowThread(flowThread)
48 , m_hasValidRegions(false)
49 , m_regionsInvalidated(false)
50 , m_regionFittingDisableCount(0)
52 setIsAnonymous(false);
55 PassRefPtr<RenderStyle> RenderFlowThread::createFlowThreadStyle(RenderStyle* parentStyle)
57 RefPtr<RenderStyle> newStyle(RenderStyle::create());
58 newStyle->inheritFrom(parentStyle);
59 newStyle->setDisplay(BLOCK);
60 newStyle->setPosition(AbsolutePosition);
61 newStyle->setZIndex(0);
62 newStyle->setLeft(Length(0, Fixed));
63 newStyle->setTop(Length(0, Fixed));
64 newStyle->setWidth(Length(100, Percent));
65 newStyle->setHeight(Length(100, Percent));
66 newStyle->font().update(0);
68 return newStyle.release();
71 void RenderFlowThread::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
73 RenderBlock::styleDidChange(diff, oldStyle);
75 if (oldStyle && oldStyle->writingMode() != style()->writingMode())
76 m_regionsInvalidated = true;
79 RenderObject* RenderFlowThread::nextRendererForNode(Node* node) const
81 FlowThreadChildList::const_iterator it = m_flowThreadChildList.begin();
82 FlowThreadChildList::const_iterator end = m_flowThreadChildList.end();
84 for (; it != end; ++it) {
85 RenderObject* child = *it;
86 ASSERT(child->node());
87 unsigned short position = node->compareDocumentPosition(child->node());
88 if (position & Node::DOCUMENT_POSITION_FOLLOWING)
95 RenderObject* RenderFlowThread::previousRendererForNode(Node* node) const
97 if (m_flowThreadChildList.isEmpty())
100 FlowThreadChildList::const_iterator begin = m_flowThreadChildList.begin();
101 FlowThreadChildList::const_iterator end = m_flowThreadChildList.end();
102 FlowThreadChildList::const_iterator it = end;
106 RenderObject* child = *it;
107 ASSERT(child->node());
108 unsigned short position = node->compareDocumentPosition(child->node());
109 if (position & Node::DOCUMENT_POSITION_PRECEDING)
111 } while (it != begin);
116 void RenderFlowThread::addFlowChild(RenderObject* newChild, RenderObject* beforeChild)
118 // The child list is used to sort the flow thread's children render objects
119 // based on their corresponding nodes DOM order. The list is needed to avoid searching the whole DOM.
121 // Do not add anonymous objects.
122 if (!newChild->node())
126 m_flowThreadChildList.insertBefore(beforeChild, newChild);
128 m_flowThreadChildList.add(newChild);
131 void RenderFlowThread::removeFlowChild(RenderObject* child)
133 m_flowThreadChildList.remove(child);
136 // Compare two regions to determine in which one the content should flow first.
137 // The function returns true if the first passed region is "less" than the second passed region.
138 // If the first region index < second region index, then the first region is "less" than the second region.
139 // If the first region index == second region index and first region appears before second region in DOM,
140 // the first region is "less" than the second region.
141 // If the first region is "less" than the second region, the first region receives content before second region.
142 static bool compareRenderRegions(const RenderRegion* firstRegion, const RenderRegion* secondRegion)
145 ASSERT(secondRegion);
147 // First, compare only region-index properties.
148 if (firstRegion->style()->regionIndex() != secondRegion->style()->regionIndex())
149 return (firstRegion->style()->regionIndex() < secondRegion->style()->regionIndex());
151 // If the regions have the same region-index, compare their position in dom.
152 ASSERT(firstRegion->node());
153 ASSERT(secondRegion->node());
155 unsigned short position = firstRegion->node()->compareDocumentPosition(secondRegion->node());
156 return (position & Node::DOCUMENT_POSITION_FOLLOWING);
159 bool RenderFlowThread::dependsOn(RenderFlowThread* otherRenderFlowThread) const
161 if (m_layoutBeforeThreadsSet.contains(otherRenderFlowThread))
164 // Recursively traverse the m_layoutBeforeThreadsSet.
165 RenderFlowThreadCountedSet::const_iterator iterator = m_layoutBeforeThreadsSet.begin();
166 RenderFlowThreadCountedSet::const_iterator end = m_layoutBeforeThreadsSet.end();
167 for (; iterator != end; ++iterator) {
168 const RenderFlowThread* beforeFlowThread = (*iterator).first;
169 if (beforeFlowThread->dependsOn(otherRenderFlowThread))
176 void RenderFlowThread::addRegionToThread(RenderRegion* renderRegion)
178 ASSERT(renderRegion);
179 if (m_regionList.isEmpty())
180 m_regionList.add(renderRegion);
182 // Find the first region "greater" than renderRegion.
183 RenderRegionList::iterator it = m_regionList.begin();
184 while (it != m_regionList.end() && !compareRenderRegions(renderRegion, *it))
186 m_regionList.insertBefore(it, renderRegion);
189 ASSERT(!renderRegion->isValid());
190 if (renderRegion->parentFlowThread()) {
191 if (renderRegion->parentFlowThread()->dependsOn(this)) {
192 // Register ourself to get a notification when the state changes.
193 renderRegion->parentFlowThread()->m_observerThreadsSet.add(this);
197 addDependencyOnFlowThread(renderRegion->parentFlowThread());
200 renderRegion->setIsValid(true);
205 void RenderFlowThread::removeRegionFromThread(RenderRegion* renderRegion)
207 ASSERT(renderRegion);
208 m_regionList.remove(renderRegion);
209 if (renderRegion->parentFlowThread()) {
210 if (!renderRegion->isValid()) {
211 renderRegion->parentFlowThread()->m_observerThreadsSet.remove(this);
212 // No need to invalidate the regions rectangles. The removed region
213 // was not taken into account. Just return here.
216 removeDependencyOnFlowThread(renderRegion->parentFlowThread());
222 void RenderFlowThread::checkInvalidRegions()
224 for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) {
225 RenderRegion* region = *iter;
226 // The only reason a region would be invalid is because it has a parent flow thread.
227 ASSERT(region->isValid() || region->parentFlowThread());
228 if (region->isValid() || region->parentFlowThread()->dependsOn(this))
231 region->parentFlowThread()->m_observerThreadsSet.remove(this);
232 addDependencyOnFlowThread(region->parentFlowThread());
233 region->setIsValid(true);
237 if (m_observerThreadsSet.isEmpty())
240 // Notify all the flow threads that were dependent on this flow.
242 // Create a copy of the list first. That's because observers might change the list when calling checkInvalidRegions.
243 Vector<RenderFlowThread*> observers;
244 copyToVector(m_observerThreadsSet, observers);
246 for (size_t i = 0; i < observers.size(); ++i) {
247 RenderFlowThread* flowThread = observers.at(i);
248 flowThread->checkInvalidRegions();
252 void RenderFlowThread::addDependencyOnFlowThread(RenderFlowThread* otherFlowThread)
254 std::pair<RenderFlowThreadCountedSet::iterator, bool> result = m_layoutBeforeThreadsSet.add(otherFlowThread);
256 // This is the first time we see this dependency. Make sure we recalculate all the dependencies.
257 view()->setIsRenderFlowThreadOrderDirty(true);
261 void RenderFlowThread::removeDependencyOnFlowThread(RenderFlowThread* otherFlowThread)
263 bool removed = m_layoutBeforeThreadsSet.remove(otherFlowThread);
265 checkInvalidRegions();
266 view()->setIsRenderFlowThreadOrderDirty(true);
270 void RenderFlowThread::pushDependencies(RenderFlowThreadList& list)
272 for (RenderFlowThreadCountedSet::iterator iter = m_layoutBeforeThreadsSet.begin(); iter != m_layoutBeforeThreadsSet.end(); ++iter) {
273 RenderFlowThread* flowThread = (*iter).first;
274 if (list.contains(flowThread))
276 flowThread->pushDependencies(list);
277 list.add(flowThread);
281 class CurrentRenderFlowThreadMaintainer {
282 WTF_MAKE_NONCOPYABLE(CurrentRenderFlowThreadMaintainer);
284 CurrentRenderFlowThreadMaintainer(RenderFlowThread* renderFlowThread)
285 : m_renderFlowThread(renderFlowThread)
287 RenderView* view = m_renderFlowThread->view();
288 ASSERT(!view->currentRenderFlowThread());
289 view->setCurrentRenderFlowThread(m_renderFlowThread);
291 ~CurrentRenderFlowThreadMaintainer()
293 RenderView* view = m_renderFlowThread->view();
294 ASSERT(view->currentRenderFlowThread() == m_renderFlowThread);
295 view->setCurrentRenderFlowThread(0);
298 RenderFlowThread* m_renderFlowThread;
301 void RenderFlowThread::layout()
303 CurrentRenderFlowThreadMaintainer currentFlowThreadSetter(this);
305 if (m_regionsInvalidated) {
306 m_regionsInvalidated = false;
308 int logicalHeight = 0;
309 for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) {
310 RenderRegion* region = *iter;
312 if (!region->isValid())
315 ASSERT(!region->needsLayout());
317 m_hasValidRegions = true;
320 if (isHorizontalWritingMode()) {
321 regionRect = IntRect(0, logicalHeight, region->contentWidth(), region->contentHeight());
322 logicalHeight += regionRect.height();
324 regionRect = IntRect(logicalHeight, 0, region->contentWidth(), region->contentHeight());
325 logicalHeight += regionRect.width();
328 region->setRegionRect(regionRect);
333 RenderBlock::layout();
336 void RenderFlowThread::computeLogicalWidth()
338 int logicalWidth = 0;
340 for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) {
341 RenderRegion* region = *iter;
342 if (!region->isValid())
344 ASSERT(!region->needsLayout());
345 logicalWidth = max(isHorizontalWritingMode() ? region->contentWidth() : region->contentHeight(), logicalWidth);
348 setLogicalWidth(logicalWidth);
351 void RenderFlowThread::computeLogicalHeight()
353 int logicalHeight = 0;
355 for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) {
356 RenderRegion* region = *iter;
357 if (!region->isValid())
359 ASSERT(!region->needsLayout());
360 logicalHeight += isHorizontalWritingMode() ? region->contentHeight() : region->contentWidth();
363 setLogicalHeight(logicalHeight);
366 void RenderFlowThread::paintIntoRegion(PaintInfo& paintInfo, const LayoutRect& regionRect, const LayoutPoint& paintOffset)
368 GraphicsContext* context = paintInfo.context;
372 // Adjust the clipping rect for the region.
373 // paintOffset contains the offset where the painting should occur
374 // adjusted with the region padding and border.
375 LayoutRect regionClippingRect(paintOffset, regionRect.size());
377 PaintInfo info(paintInfo);
378 info.rect.intersect(regionClippingRect);
380 if (!info.rect.isEmpty()) {
383 context->clip(regionClippingRect);
385 // RenderFlowThread should start painting its content in a position that is offset
386 // from the region rect's current position. The amount of offset is equal to the location of
387 // region in flow coordinates.
388 LayoutPoint renderFlowThreadOffset;
389 if (style()->isFlippedBlocksWritingMode()) {
390 LayoutRect flippedRegionRect(regionRect);
391 flipForWritingMode(flippedRegionRect);
392 renderFlowThreadOffset = LayoutPoint(regionClippingRect.location() - flippedRegionRect.location());
394 renderFlowThreadOffset = LayoutPoint(regionClippingRect.location() - regionRect.location());
396 context->translate(renderFlowThreadOffset.x(), renderFlowThreadOffset.y());
397 info.rect.moveBy(-renderFlowThreadOffset);
399 layer()->paint(context, info.rect);
405 bool RenderFlowThread::hitTestRegion(const LayoutRect& regionRect, const HitTestRequest& request, HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset)
407 LayoutRect regionClippingRect(accumulatedOffset, regionRect.size());
408 if (!regionClippingRect.contains(pointInContainer))
411 LayoutPoint renderFlowThreadOffset;
412 if (style()->isFlippedBlocksWritingMode()) {
413 LayoutRect flippedRegionRect(regionRect);
414 flipForWritingMode(flippedRegionRect);
415 renderFlowThreadOffset = LayoutPoint(regionClippingRect.location() - flippedRegionRect.location());
417 renderFlowThreadOffset = LayoutPoint(regionClippingRect.location() - regionRect.location());
419 LayoutPoint transformedPoint(pointInContainer.x() - renderFlowThreadOffset.x(), pointInContainer.y() - renderFlowThreadOffset.y());
421 // Always ignore clipping, since the RenderFlowThread has nothing to do with the bounds of the FrameView.
422 HitTestRequest newRequest(request.type() & HitTestRequest::IgnoreClipping);
424 LayoutPoint oldPoint = result.point();
425 result.setPoint(transformedPoint);
426 bool isPointInsideFlowThread = layer()->hitTest(newRequest, result);
427 result.setPoint(oldPoint);
429 // FIXME: Should we set result.m_localPoint back to the RenderRegion's coordinate space or leave it in the RenderFlowThread's coordinate
430 // space? Right now it's staying in the RenderFlowThread's coordinate space, which may end up being ok. We will know more when we get around to
431 // patching positionForPoint.
432 return isPointInsideFlowThread;
435 bool RenderFlowThread::shouldRepaint(const LayoutRect& r) const
437 if (view()->printing() || r.isEmpty())
443 void RenderFlowThread::repaintRectangleInRegions(const LayoutRect& repaintRect, bool immediate)
445 if (!shouldRepaint(repaintRect))
448 for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) {
449 RenderRegion* region = *iter;
450 if (!region->isValid())
453 // We only have to issue a repaint in this region if the region rect intersects the repaint rect.
454 LayoutRect flippedRegionRect(region->regionRect());
455 flipForWritingMode(flippedRegionRect); // Put the region rect into physical coordinates.
457 IntRect clippedRect(flippedRegionRect);
458 clippedRect.intersect(repaintRect);
459 if (clippedRect.isEmpty())
462 // Put the region rect into the region's physical coordinate space.
463 clippedRect.setLocation(region->contentBoxRect().location() + (repaintRect.location() - flippedRegionRect.location()));
465 // Now switch to the region's writing mode coordinate space and let it repaint itself.
466 region->flipForWritingMode(clippedRect);
467 region->repaintRectangle(clippedRect, immediate);
471 RenderRegion* RenderFlowThread::renderRegionForLine(LayoutUnit position, bool extendLastRegion) const
473 ASSERT(!m_regionsInvalidated);
475 // If no region matches the position and extendLastRegion is true, it will return
476 // the last valid region. It is similar to auto extending the size of the last region.
477 RenderRegion* lastValidRegion = 0;
479 // FIXME: The regions are always in order, optimize this search.
480 bool useHorizontalWritingMode = isHorizontalWritingMode();
481 for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) {
482 RenderRegion* region = *iter;
483 if (!region->isValid())
489 LayoutRect regionRect = region->regionRect();
491 if (useHorizontalWritingMode) {
492 if (regionRect.y() <= position && position < regionRect.maxY())
497 if (regionRect.x() <= position && position < regionRect.maxX())
500 if (extendLastRegion)
501 lastValidRegion = region;
504 return lastValidRegion;
507 LayoutUnit RenderFlowThread::regionLogicalWidthForLine(LayoutUnit position) const
509 const bool extendLastRegion = true;
510 RenderRegion* region = renderRegionForLine(position, extendLastRegion);
514 return isHorizontalWritingMode() ? region->regionRect().width() : region->regionRect().height();
518 RenderRegion* RenderFlowThread::mapFromFlowToRegion(TransformState& transformState) const
520 if (!hasValidRegions())
523 LayoutRect boxRect = transformState.mappedQuad().enclosingBoundingBox();
524 flipForWritingMode(boxRect);
526 // FIXME: We need to refactor RenderObject::absoluteQuads to be able to split the quads across regions,
527 // for now we just take the center of the mapped enclosing box and map it to a region.
528 // Note: Using the center in order to avoid rounding errors.
530 const bool extendLastRegion = true;
531 LayoutPoint center = boxRect.center();
532 RenderRegion* renderRegion = renderRegionForLine(isHorizontalWritingMode() ? center.y() : center.x(), extendLastRegion);
536 LayoutRect flippedRegionRect(renderRegion->regionRect());
537 flipForWritingMode(flippedRegionRect);
539 transformState.move(renderRegion->contentBoxRect().location() - flippedRegionRect.location());
544 } // namespace WebCore