1 /* 2 * Copyright (C) 2012 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS IN..0TERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "core/rendering/RenderNamedFlowThread.h" 28 29 #include "RuntimeEnabledFeatures.h" 30 #include "bindings/v8/ExceptionStatePlaceholder.h" 31 #include "core/dom/NamedFlow.h" 32 #include "core/dom/NodeRenderingTraversal.h" 33 #include "core/dom/NodeTraversal.h" 34 #include "core/dom/Position.h" 35 #include "core/dom/Range.h" 36 #include "core/dom/Text.h" 37 #include "core/inspector/InspectorInstrumentation.h" 38 #include "core/rendering/FlowThreadController.h" 39 #include "core/rendering/InlineTextBox.h" 40 #include "core/rendering/RenderInline.h" 41 #include "core/rendering/RenderRegion.h" 42 #include "core/rendering/RenderText.h" 43 #include "core/rendering/RenderView.h" 44 45 namespace WebCore { 46 47 RenderNamedFlowThread* RenderNamedFlowThread::createAnonymous(Document* document, PassRefPtr<NamedFlow> namedFlow) 48 { 49 ASSERT(RuntimeEnabledFeatures::cssRegionsEnabled()); 50 RenderNamedFlowThread* renderer = new RenderNamedFlowThread(namedFlow); 51 renderer->setDocumentForAnonymous(document); 52 return renderer; 53 } 54 55 RenderNamedFlowThread::RenderNamedFlowThread(PassRefPtr<NamedFlow> namedFlow) 56 : m_overset(true) 57 , m_namedFlow(namedFlow) 58 , m_regionLayoutUpdateEventTimer(this, &RenderNamedFlowThread::regionLayoutUpdateEventTimerFired) 59 , m_regionOversetChangeEventTimer(this, &RenderNamedFlowThread::regionOversetChangeEventTimerFired) 60 { 61 } 62 63 RenderNamedFlowThread::~RenderNamedFlowThread() 64 { 65 // The flow thread can be destroyed without unregistering the content nodes if the document is destroyed. 66 // This can lead to problems because the nodes are still marked as belonging to a flow thread. 67 clearContentNodes(); 68 69 // Also leave the NamedFlow object in a consistent state by calling mark for destruction. 70 setMarkForDestruction(); 71 } 72 73 const char* RenderNamedFlowThread::renderName() const 74 { 75 return "RenderNamedFlowThread"; 76 } 77 78 void RenderNamedFlowThread::clearContentNodes() 79 { 80 for (NamedFlowContentNodes::iterator it = m_contentNodes.begin(); it != m_contentNodes.end(); ++it) { 81 Node* contentNode = *it; 82 83 ASSERT(contentNode && contentNode->isElementNode()); 84 ASSERT(contentNode->inNamedFlow()); 85 ASSERT(contentNode->document() == document()); 86 87 contentNode->clearInNamedFlow(); 88 } 89 90 m_contentNodes.clear(); 91 } 92 93 void RenderNamedFlowThread::updateWritingMode() 94 { 95 RenderRegion* firstRegion = m_regionList.first(); 96 if (!firstRegion) 97 return; 98 if (style()->writingMode() == firstRegion->style()->writingMode()) 99 return; 100 101 // The first region defines the principal writing mode for the entire flow. 102 RefPtr<RenderStyle> newStyle = RenderStyle::clone(style()); 103 newStyle->setWritingMode(firstRegion->style()->writingMode()); 104 setStyle(newStyle.release()); 105 } 106 107 RenderObject* RenderNamedFlowThread::nextRendererForNode(Node* node) const 108 { 109 FlowThreadChildList::const_iterator it = m_flowThreadChildList.begin(); 110 FlowThreadChildList::const_iterator end = m_flowThreadChildList.end(); 111 112 for (; it != end; ++it) { 113 RenderObject* child = *it; 114 ASSERT(child->node()); 115 unsigned short position = node->compareDocumentPosition(child->node()); 116 if (position & Node::DOCUMENT_POSITION_FOLLOWING) 117 return child; 118 } 119 120 return 0; 121 } 122 123 RenderObject* RenderNamedFlowThread::previousRendererForNode(Node* node) const 124 { 125 if (m_flowThreadChildList.isEmpty()) 126 return 0; 127 128 FlowThreadChildList::const_iterator begin = m_flowThreadChildList.begin(); 129 FlowThreadChildList::const_iterator end = m_flowThreadChildList.end(); 130 FlowThreadChildList::const_iterator it = end; 131 132 do { 133 --it; 134 RenderObject* child = *it; 135 ASSERT(child->node()); 136 unsigned short position = node->compareDocumentPosition(child->node()); 137 if (position & Node::DOCUMENT_POSITION_PRECEDING) 138 return child; 139 } while (it != begin); 140 141 return 0; 142 } 143 144 void RenderNamedFlowThread::addFlowChild(RenderObject* newChild) 145 { 146 // The child list is used to sort the flow thread's children render objects 147 // based on their corresponding nodes DOM order. The list is needed to avoid searching the whole DOM. 148 149 Node* childNode = newChild->node(); 150 151 // Do not add anonymous objects. 152 if (!childNode) 153 return; 154 155 ASSERT(childNode->isElementNode()); 156 157 RenderObject* beforeChild = nextRendererForNode(childNode); 158 if (beforeChild) 159 m_flowThreadChildList.insertBefore(beforeChild, newChild); 160 else 161 m_flowThreadChildList.add(newChild); 162 } 163 164 void RenderNamedFlowThread::removeFlowChild(RenderObject* child) 165 { 166 m_flowThreadChildList.remove(child); 167 } 168 169 bool RenderNamedFlowThread::dependsOn(RenderNamedFlowThread* otherRenderFlowThread) const 170 { 171 if (m_layoutBeforeThreadsSet.contains(otherRenderFlowThread)) 172 return true; 173 174 // Recursively traverse the m_layoutBeforeThreadsSet. 175 RenderNamedFlowThreadCountedSet::const_iterator iterator = m_layoutBeforeThreadsSet.begin(); 176 RenderNamedFlowThreadCountedSet::const_iterator end = m_layoutBeforeThreadsSet.end(); 177 for (; iterator != end; ++iterator) { 178 const RenderNamedFlowThread* beforeFlowThread = (*iterator).key; 179 if (beforeFlowThread->dependsOn(otherRenderFlowThread)) 180 return true; 181 } 182 183 return false; 184 } 185 186 // Compare two regions to determine in which one the content should flow first. 187 // The function returns true if the first passed region is "less" than the second passed region. 188 // If the first region appears before second region in DOM, 189 // the first region is "less" than the second region. 190 // If the first region is "less" than the second region, the first region receives content before second region. 191 static bool compareRenderRegions(const RenderRegion* firstRegion, const RenderRegion* secondRegion) 192 { 193 ASSERT(firstRegion); 194 ASSERT(secondRegion); 195 196 ASSERT(firstRegion->generatingNodeForRegion()); 197 ASSERT(secondRegion->generatingNodeForRegion()); 198 199 // If the regions belong to different nodes, compare their position in the DOM. 200 if (firstRegion->generatingNodeForRegion() != secondRegion->generatingNodeForRegion()) { 201 unsigned short position = firstRegion->generatingNodeForRegion()->compareDocumentPosition(secondRegion->generatingNodeForRegion()); 202 203 // If the second region is contained in the first one, the first region is "less" if it's :before. 204 if (position & Node::DOCUMENT_POSITION_CONTAINED_BY) { 205 ASSERT(secondRegion->style()->styleType() == NOPSEUDO); 206 return firstRegion->style()->styleType() == BEFORE; 207 } 208 209 // If the second region contains the first region, the first region is "less" if the second is :after. 210 if (position & Node::DOCUMENT_POSITION_CONTAINS) { 211 ASSERT(firstRegion->style()->styleType() == NOPSEUDO); 212 return secondRegion->style()->styleType() == AFTER; 213 } 214 215 return (position & Node::DOCUMENT_POSITION_FOLLOWING); 216 } 217 218 // FIXME: Currently it's not possible for an element to be both a region and have pseudo-children. The case is covered anyway. 219 switch (firstRegion->style()->styleType()) { 220 case BEFORE: 221 // The second region can be the node or the after pseudo-element (before is smaller than any of those). 222 return true; 223 case AFTER: 224 // The second region can be the node or the before pseudo-element (after is greater than any of those). 225 return false; 226 case NOPSEUDO: 227 // The second region can either be the before or the after pseudo-element (the node is only smaller than the after pseudo-element). 228 return firstRegion->style()->styleType() == AFTER; 229 default: 230 break; 231 } 232 233 ASSERT_NOT_REACHED(); 234 return true; 235 } 236 237 // This helper function adds a region to a list preserving the order property of the list. 238 static void addRegionToList(RenderRegionList& regionList, RenderRegion* renderRegion) 239 { 240 if (regionList.isEmpty()) { 241 regionList.add(renderRegion); 242 } else { 243 // Find the first region "greater" than renderRegion. 244 RenderRegionList::iterator it = regionList.begin(); 245 while (it != regionList.end() && !compareRenderRegions(renderRegion, *it)) 246 ++it; 247 regionList.insertBefore(it, renderRegion); 248 } 249 } 250 251 void RenderNamedFlowThread::addRegionToNamedFlowThread(RenderRegion* renderRegion) 252 { 253 ASSERT(renderRegion); 254 ASSERT(!renderRegion->isValid()); 255 256 if (renderRegion->parentNamedFlowThread()) 257 addDependencyOnFlowThread(renderRegion->parentNamedFlowThread()); 258 259 renderRegion->setIsValid(true); 260 addRegionToList(m_regionList, renderRegion); 261 262 if (m_regionList.first() == renderRegion) 263 updateWritingMode(); 264 } 265 266 void RenderNamedFlowThread::addRegionToThread(RenderRegion* renderRegion) 267 { 268 ASSERT(renderRegion); 269 ASSERT(!renderRegion->isValid()); 270 271 resetMarkForDestruction(); 272 273 if (renderRegion->parentNamedFlowThread() && renderRegion->parentNamedFlowThread()->dependsOn(this)) { 274 // The order of invalid regions is irrelevant. 275 m_invalidRegionList.add(renderRegion); 276 // Register ourself to get a notification when the state changes. 277 renderRegion->parentNamedFlowThread()->m_observerThreadsSet.add(this); 278 return; 279 } 280 281 addRegionToNamedFlowThread(renderRegion); 282 283 invalidateRegions(); 284 } 285 286 void RenderNamedFlowThread::removeRegionFromThread(RenderRegion* renderRegion) 287 { 288 ASSERT(renderRegion); 289 290 if (renderRegion->parentNamedFlowThread()) { 291 if (!renderRegion->isValid()) { 292 ASSERT(m_invalidRegionList.contains(renderRegion)); 293 m_invalidRegionList.remove(renderRegion); 294 renderRegion->parentNamedFlowThread()->m_observerThreadsSet.remove(this); 295 // No need to invalidate the regions rectangles. The removed region 296 // was not taken into account. Just return here. 297 return; 298 } 299 removeDependencyOnFlowThread(renderRegion->parentNamedFlowThread()); 300 } 301 302 ASSERT(m_regionList.contains(renderRegion)); 303 bool wasFirst = m_regionList.first() == renderRegion; 304 m_regionList.remove(renderRegion); 305 306 if (canBeDestroyed()) 307 setMarkForDestruction(); 308 309 // After removing all the regions in the flow the following layout needs to dispatch the regionLayoutUpdate event 310 if (m_regionList.isEmpty()) 311 setDispatchRegionLayoutUpdateEvent(true); 312 else if (wasFirst) 313 updateWritingMode(); 314 315 invalidateRegions(); 316 } 317 318 void RenderNamedFlowThread::regionChangedWritingMode(RenderRegion* region) 319 { 320 if (m_regionList.first() == region) 321 updateWritingMode(); 322 } 323 324 void RenderNamedFlowThread::computeOversetStateForRegions(LayoutUnit oldClientAfterEdge) 325 { 326 LayoutUnit height = oldClientAfterEdge; 327 328 // FIXME: the visual overflow of middle region (if it is the last one to contain any content in a render flow thread) 329 // might not be taken into account because the render flow thread height is greater that that regions height + its visual overflow 330 // because of how computeLogicalHeight is implemented for RenderFlowThread (as a sum of all regions height). 331 // This means that the middle region will be marked as fit (even if it has visual overflow flowing into the next region) 332 if (hasRenderOverflow() 333 && ( (isHorizontalWritingMode() && visualOverflowRect().maxY() > clientBoxRect().maxY()) 334 || (!isHorizontalWritingMode() && visualOverflowRect().maxX() > clientBoxRect().maxX()))) 335 height = isHorizontalWritingMode() ? visualOverflowRect().maxY() : visualOverflowRect().maxX(); 336 337 RenderRegion* lastReg = lastRegion(); 338 for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { 339 RenderRegion* region = *iter; 340 LayoutUnit flowMin = height - (isHorizontalWritingMode() ? region->flowThreadPortionRect().y() : region->flowThreadPortionRect().x()); 341 LayoutUnit flowMax = height - (isHorizontalWritingMode() ? region->flowThreadPortionRect().maxY() : region->flowThreadPortionRect().maxX()); 342 RegionOversetState previousState = region->regionOversetState(); 343 RegionOversetState state = RegionFit; 344 if (flowMin <= 0) 345 state = RegionEmpty; 346 if (flowMax > 0 && region == lastReg) 347 state = RegionOverset; 348 region->setRegionOversetState(state); 349 // determine whether the NamedFlow object should dispatch a regionLayoutUpdate event 350 // FIXME: currently it cannot determine whether a region whose regionOverset state remained either "fit" or "overset" has actually 351 // changed, so it just assumes that the NamedFlow should dispatch the event 352 if (previousState != state 353 || state == RegionFit 354 || state == RegionOverset) 355 setDispatchRegionLayoutUpdateEvent(true); 356 357 if (previousState != state) 358 setDispatchRegionOversetChangeEvent(true); 359 } 360 361 // If the number of regions has changed since we last computed the overset property, schedule the regionOversetChange event. 362 if (previousRegionCountChanged()) { 363 setDispatchRegionOversetChangeEvent(true); 364 updatePreviousRegionCount(); 365 } 366 367 // With the regions overflow state computed we can also set the overset flag for the named flow. 368 // If there are no valid regions in the chain, overset is true. 369 m_overset = lastReg ? lastReg->regionOversetState() == RegionOverset : true; 370 } 371 372 void RenderNamedFlowThread::checkInvalidRegions() 373 { 374 Vector<RenderRegion*> newValidRegions; 375 for (RenderRegionList::iterator iter = m_invalidRegionList.begin(); iter != m_invalidRegionList.end(); ++iter) { 376 RenderRegion* region = *iter; 377 // The only reason a region would be invalid is because it has a parent flow thread. 378 ASSERT(!region->isValid() && region->parentNamedFlowThread()); 379 if (region->parentNamedFlowThread()->dependsOn(this)) 380 continue; 381 382 newValidRegions.append(region); 383 } 384 385 for (Vector<RenderRegion*>::iterator iter = newValidRegions.begin(); iter != newValidRegions.end(); ++iter) { 386 RenderRegion* region = *iter; 387 m_invalidRegionList.remove(region); 388 region->parentNamedFlowThread()->m_observerThreadsSet.remove(this); 389 addRegionToNamedFlowThread(region); 390 } 391 392 if (!newValidRegions.isEmpty()) 393 invalidateRegions(); 394 395 if (m_observerThreadsSet.isEmpty()) 396 return; 397 398 // Notify all the flow threads that were dependent on this flow. 399 400 // Create a copy of the list first. That's because observers might change the list when calling checkInvalidRegions. 401 Vector<RenderNamedFlowThread*> observers; 402 copyToVector(m_observerThreadsSet, observers); 403 404 for (size_t i = 0; i < observers.size(); ++i) { 405 RenderNamedFlowThread* flowThread = observers.at(i); 406 flowThread->checkInvalidRegions(); 407 } 408 } 409 410 void RenderNamedFlowThread::addDependencyOnFlowThread(RenderNamedFlowThread* otherFlowThread) 411 { 412 RenderNamedFlowThreadCountedSet::AddResult result = m_layoutBeforeThreadsSet.add(otherFlowThread); 413 if (result.isNewEntry) { 414 // This is the first time we see this dependency. Make sure we recalculate all the dependencies. 415 view()->flowThreadController()->setIsRenderNamedFlowThreadOrderDirty(true); 416 } 417 } 418 419 void RenderNamedFlowThread::removeDependencyOnFlowThread(RenderNamedFlowThread* otherFlowThread) 420 { 421 bool removed = m_layoutBeforeThreadsSet.remove(otherFlowThread); 422 if (removed) { 423 checkInvalidRegions(); 424 view()->flowThreadController()->setIsRenderNamedFlowThreadOrderDirty(true); 425 } 426 } 427 428 void RenderNamedFlowThread::pushDependencies(RenderNamedFlowThreadList& list) 429 { 430 for (RenderNamedFlowThreadCountedSet::iterator iter = m_layoutBeforeThreadsSet.begin(); iter != m_layoutBeforeThreadsSet.end(); ++iter) { 431 RenderNamedFlowThread* flowThread = (*iter).key; 432 if (list.contains(flowThread)) 433 continue; 434 flowThread->pushDependencies(list); 435 list.add(flowThread); 436 } 437 } 438 439 // The content nodes list contains those nodes with -webkit-flow-into: flow. 440 // An element with display:none should also be listed among those nodes. 441 // The list of nodes is ordered. 442 void RenderNamedFlowThread::registerNamedFlowContentNode(Node* contentNode) 443 { 444 ASSERT(contentNode && contentNode->isElementNode()); 445 ASSERT(contentNode->document() == document()); 446 447 contentNode->setInNamedFlow(); 448 449 resetMarkForDestruction(); 450 451 // Find the first content node following the new content node. 452 for (NamedFlowContentNodes::iterator it = m_contentNodes.begin(); it != m_contentNodes.end(); ++it) { 453 Node* node = *it; 454 unsigned short position = contentNode->compareDocumentPosition(node); 455 if (position & Node::DOCUMENT_POSITION_FOLLOWING) { 456 m_contentNodes.insertBefore(node, contentNode); 457 return; 458 } 459 } 460 m_contentNodes.add(contentNode); 461 } 462 463 void RenderNamedFlowThread::unregisterNamedFlowContentNode(Node* contentNode) 464 { 465 ASSERT(contentNode && contentNode->isElementNode()); 466 ASSERT(m_contentNodes.contains(contentNode)); 467 ASSERT(contentNode->inNamedFlow()); 468 ASSERT(contentNode->document() == document()); 469 470 contentNode->clearInNamedFlow(); 471 m_contentNodes.remove(contentNode); 472 473 if (canBeDestroyed()) 474 setMarkForDestruction(); 475 } 476 477 const AtomicString& RenderNamedFlowThread::flowThreadName() const 478 { 479 return m_namedFlow->name(); 480 } 481 482 bool RenderNamedFlowThread::isChildAllowed(RenderObject* child, RenderStyle* style) const 483 { 484 if (!child->node()) 485 return true; 486 487 ASSERT(child->node()->isElementNode()); 488 Node* originalParent = NodeRenderingTraversal::parent(child->node()); 489 if (!originalParent || !originalParent->renderer()) 490 return true; 491 492 return originalParent->renderer()->isChildAllowed(child, style); 493 } 494 495 void RenderNamedFlowThread::dispatchRegionLayoutUpdateEvent() 496 { 497 RenderFlowThread::dispatchRegionLayoutUpdateEvent(); 498 InspectorInstrumentation::didUpdateRegionLayout(&document(), m_namedFlow.get()); 499 500 if (!m_regionLayoutUpdateEventTimer.isActive() && m_namedFlow->hasEventListeners()) 501 m_regionLayoutUpdateEventTimer.startOneShot(0); 502 } 503 504 void RenderNamedFlowThread::dispatchRegionOversetChangeEvent() 505 { 506 RenderFlowThread::dispatchRegionOversetChangeEvent(); 507 InspectorInstrumentation::didChangeRegionOverset(&document(), m_namedFlow.get()); 508 509 if (!m_regionOversetChangeEventTimer.isActive() && m_namedFlow->hasEventListeners()) 510 m_regionOversetChangeEventTimer.startOneShot(0); 511 } 512 513 void RenderNamedFlowThread::regionLayoutUpdateEventTimerFired(Timer<RenderNamedFlowThread>*) 514 { 515 ASSERT(m_namedFlow); 516 517 m_namedFlow->dispatchRegionLayoutUpdateEvent(); 518 } 519 520 void RenderNamedFlowThread::regionOversetChangeEventTimerFired(Timer<RenderNamedFlowThread>*) 521 { 522 ASSERT(m_namedFlow); 523 524 m_namedFlow->dispatchRegionOversetChangeEvent(); 525 } 526 527 void RenderNamedFlowThread::setMarkForDestruction() 528 { 529 if (m_namedFlow->flowState() == NamedFlow::FlowStateNull) 530 return; 531 532 m_namedFlow->setRenderer(0); 533 // After this call ends, the renderer can be safely destroyed. 534 // The NamedFlow object may outlive its renderer if it's referenced from a script and may be reatached to one if the named flow is recreated in the stylesheet. 535 } 536 537 void RenderNamedFlowThread::resetMarkForDestruction() 538 { 539 if (m_namedFlow->flowState() == NamedFlow::FlowStateCreated) 540 return; 541 542 m_namedFlow->setRenderer(this); 543 } 544 545 bool RenderNamedFlowThread::isMarkedForDestruction() const 546 { 547 // Flow threads in the "NULL" state can be destroyed. 548 return m_namedFlow->flowState() == NamedFlow::FlowStateNull; 549 } 550 551 static bool isContainedInNodes(Vector<Node*> others, Node* node) 552 { 553 for (size_t i = 0; i < others.size(); i++) { 554 Node* other = others.at(i); 555 if (other->contains(node)) 556 return true; 557 } 558 return false; 559 } 560 561 static bool boxIntersectsRegion(LayoutUnit logicalTopForBox, LayoutUnit logicalBottomForBox, LayoutUnit logicalTopForRegion, LayoutUnit logicalBottomForRegion) 562 { 563 bool regionIsEmpty = logicalBottomForRegion != LayoutUnit::max() && logicalTopForRegion != LayoutUnit::min() 564 && (logicalBottomForRegion - logicalTopForRegion) <= 0; 565 return (logicalBottomForBox - logicalTopForBox) > 0 566 && !regionIsEmpty 567 && logicalTopForBox < logicalBottomForRegion && logicalTopForRegion < logicalBottomForBox; 568 } 569 570 // Retrieve the next node to be visited while computing the ranges inside a region. 571 static Node* nextNodeInsideContentNode(const Node& currNode, const Node* contentNode) 572 { 573 ASSERT(contentNode && contentNode->inNamedFlow()); 574 575 if (currNode.renderer() && currNode.renderer()->isSVGRoot()) 576 return NodeTraversal::nextSkippingChildren(currNode, contentNode); 577 return NodeTraversal::next(currNode, contentNode); 578 } 579 580 void RenderNamedFlowThread::getRanges(Vector<RefPtr<Range> >& rangeObjects, const RenderRegion* region) const 581 { 582 LayoutUnit logicalTopForRegion; 583 LayoutUnit logicalBottomForRegion; 584 585 // extend the first region top to contain everything up to its logical height 586 if (region->isFirstRegion()) 587 logicalTopForRegion = LayoutUnit::min(); 588 else 589 logicalTopForRegion = region->logicalTopForFlowThreadContent(); 590 591 // extend the last region to contain everything above its y() 592 if (region->isLastRegion()) 593 logicalBottomForRegion = LayoutUnit::max(); 594 else 595 logicalBottomForRegion = region->logicalBottomForFlowThreadContent(); 596 597 Vector<Node*> nodes; 598 // eliminate the contentNodes that are descendants of other contentNodes 599 for (NamedFlowContentNodes::const_iterator it = contentNodes().begin(); it != contentNodes().end(); ++it) { 600 Node* node = *it; 601 if (!isContainedInNodes(nodes, node)) 602 nodes.append(node); 603 } 604 605 for (size_t i = 0; i < nodes.size(); i++) { 606 Node* contentNode = nodes.at(i); 607 if (!contentNode->renderer()) 608 continue; 609 610 RefPtr<Range> range = Range::create(contentNode->document()); 611 bool foundStartPosition = false; 612 bool startsAboveRegion = true; 613 bool endsBelowRegion = true; 614 bool skipOverOutsideNodes = false; 615 Node* lastEndNode = 0; 616 617 for (Node* node = contentNode; node; node = nextNodeInsideContentNode(*node, contentNode)) { 618 RenderObject* renderer = node->renderer(); 619 if (!renderer) 620 continue; 621 622 LayoutRect boundingBox; 623 if (renderer->isRenderInline()) { 624 boundingBox = toRenderInline(renderer)->linesBoundingBox(); 625 } else if (renderer->isText()) { 626 boundingBox = toRenderText(renderer)->linesBoundingBox(); 627 } else { 628 boundingBox = toRenderBox(renderer)->frameRect(); 629 if (toRenderBox(renderer)->isRelPositioned()) 630 boundingBox.move(toRenderBox(renderer)->relativePositionLogicalOffset()); 631 } 632 633 LayoutUnit offsetTop = renderer->containingBlock()->offsetFromLogicalTopOfFirstPage(); 634 const LayoutPoint logicalOffsetFromTop(isHorizontalWritingMode() ? LayoutUnit() : offsetTop, 635 isHorizontalWritingMode() ? offsetTop : LayoutUnit()); 636 637 boundingBox.moveBy(logicalOffsetFromTop); 638 639 LayoutUnit logicalTopForRenderer = region->logicalTopOfFlowThreadContentRect(boundingBox); 640 LayoutUnit logicalBottomForRenderer = region->logicalBottomOfFlowThreadContentRect(boundingBox); 641 642 // if the bounding box of the current element doesn't intersect the region box 643 // close the current range only if the start element began inside the region, 644 // otherwise just move the start position after this node and keep skipping them until we found a proper start position. 645 if (!boxIntersectsRegion(logicalTopForRenderer, logicalBottomForRenderer, logicalTopForRegion, logicalBottomForRegion)) { 646 if (foundStartPosition) { 647 if (!startsAboveRegion) { 648 if (range->intersectsNode(node, IGNORE_EXCEPTION)) 649 range->setEndBefore(node, IGNORE_EXCEPTION); 650 rangeObjects.append(range->cloneRange(IGNORE_EXCEPTION)); 651 range = Range::create(contentNode->document()); 652 startsAboveRegion = true; 653 } else { 654 skipOverOutsideNodes = true; 655 } 656 } 657 if (skipOverOutsideNodes) 658 range->setStartAfter(node, IGNORE_EXCEPTION); 659 foundStartPosition = false; 660 continue; 661 } 662 663 // start position 664 if (logicalTopForRenderer < logicalTopForRegion && startsAboveRegion) { 665 if (renderer->isText()) { // Text crosses region top 666 // for Text elements, just find the last textbox that is contained inside the region and use its start() offset as start position 667 RenderText* textRenderer = toRenderText(renderer); 668 for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 669 if (offsetTop + box->logicalBottom() < logicalTopForRegion) 670 continue; 671 range->setStart(Position(toText(node), box->start())); 672 startsAboveRegion = false; 673 break; 674 } 675 } else { // node crosses region top 676 // for all elements, except Text, just set the start position to be before their children 677 startsAboveRegion = true; 678 range->setStart(Position(node, Position::PositionIsBeforeChildren)); 679 } 680 } else { // node starts inside region 681 // for elements that start inside the region, set the start position to be before them. If we found one, we will just skip the others until 682 // the range is closed. 683 if (startsAboveRegion) { 684 startsAboveRegion = false; 685 range->setStartBefore(node, IGNORE_EXCEPTION); 686 } 687 } 688 skipOverOutsideNodes = false; 689 foundStartPosition = true; 690 691 // end position 692 if (logicalBottomForRegion < logicalBottomForRenderer && (endsBelowRegion || (!endsBelowRegion && !node->isDescendantOf(lastEndNode)))) { 693 // for Text elements, just find just find the last textbox that is contained inside the region and use its start()+len() offset as end position 694 if (renderer->isText()) { // Text crosses region bottom 695 RenderText* textRenderer = toRenderText(renderer); 696 InlineTextBox* lastBox = 0; 697 for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 698 if ((offsetTop + box->logicalTop()) < logicalBottomForRegion) { 699 lastBox = box; 700 continue; 701 } 702 ASSERT(lastBox); 703 if (lastBox) 704 range->setEnd(Position(toText(node), lastBox->start() + lastBox->len())); 705 break; 706 } 707 endsBelowRegion = false; 708 lastEndNode = node; 709 } else { // node crosses region bottom 710 // for all elements, except Text, just set the start position to be after their children 711 range->setEnd(Position(node, Position::PositionIsAfterChildren)); 712 endsBelowRegion = true; 713 lastEndNode = node; 714 } 715 } else { // node ends inside region 716 // for elements that ends inside the region, set the end position to be after them 717 // allow this end position to be changed only by other elements that are not descendants of the current end node 718 if (endsBelowRegion || (!endsBelowRegion && !node->isDescendantOf(lastEndNode))) { 719 range->setEndAfter(node, IGNORE_EXCEPTION); 720 endsBelowRegion = false; 721 lastEndNode = node; 722 } 723 } 724 } 725 if (foundStartPosition || skipOverOutsideNodes) 726 rangeObjects.append(range); 727 } 728 } 729 730 } 731