OLD | NEW |
| (Empty) |
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::regionLayoutU
pdateEventTimerFired) | |
59 , m_regionOversetChangeEventTimer(this, &RenderNamedFlowThread::regionOverse
tChangeEventTimerFired) | |
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_co
ntentNodes.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* otherRenderFlowThre
ad) const | |
170 { | |
171 if (m_layoutBeforeThreadsSet.contains(otherRenderFlowThread)) | |
172 return true; | |
173 | |
174 // Recursively traverse the m_layoutBeforeThreadsSet. | |
175 RenderNamedFlowThreadCountedSet::const_iterator iterator = m_layoutBeforeThr
eadsSet.begin(); | |
176 RenderNamedFlowThreadCountedSet::const_iterator end = m_layoutBeforeThreadsS
et.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 secon
d 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 receiv
es content before second region. | |
191 static bool compareRenderRegions(const RenderRegion* firstRegion, const RenderRe
gion* 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 D
OM. | |
200 if (firstRegion->generatingNodeForRegion() != secondRegion->generatingNodeFo
rRegion()) { | |
201 unsigned short position = firstRegion->generatingNodeForRegion()->compar
eDocumentPosition(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-elemen
t (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* renderRe
gion) | |
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* renderRegio
n) | |
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->parentNamedFlowTh
read()->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(t
his); | |
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 oldClientAf
terEdge) | |
325 { | |
326 LayoutUnit height = oldClientAfterEdge; | |
327 | |
328 // FIXME: the visual overflow of middle region (if it is the last one to con
tain 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 v
isual overflow flowing into the next region) | |
332 if (hasRenderOverflow() | |
333 && ( (isHorizontalWritingMode() && visualOverflowRect().maxY() > clientB
oxRect().maxY()) | |
334 || (!isHorizontalWritingMode() && visualOverflowRect().maxX() > clie
ntBoxRect().maxX()))) | |
335 height = isHorizontalWritingMode() ? visualOverflowRect().maxY() : visua
lOverflowRect().maxX(); | |
336 | |
337 RenderRegion* lastReg = lastRegion(); | |
338 for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regio
nList.end(); ++iter) { | |
339 RenderRegion* region = *iter; | |
340 LayoutUnit flowMin = height - (isHorizontalWritingMode() ? region->flowT
hreadPortionRect().y() : region->flowThreadPortionRect().x()); | |
341 LayoutUnit flowMax = height - (isHorizontalWritingMode() ? region->flowT
hreadPortionRect().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 regionLayout
Update event | |
350 // FIXME: currently it cannot determine whether a region whose regionOve
rset state remained either "fit" or "overset" has actually | |
351 // changed, so it just assumes that the NamedFlow should dispatch the ev
ent | |
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 p
roperty, 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 th
e 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* oth
erFlowThread) | |
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 recalcula
te all the dependencies. | |
415 view()->flowThreadController()->setIsRenderNamedFlowThreadOrderDirty(tru
e); | |
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(tru
e); | |
425 } | |
426 } | |
427 | |
428 void RenderNamedFlowThread::pushDependencies(RenderNamedFlowThreadList& list) | |
429 { | |
430 for (RenderNamedFlowThreadCountedSet::iterator iter = m_layoutBeforeThreadsS
et.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_co
ntentNodes.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* sty
le) 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->hasEventListe
ners()) | |
501 m_regionLayoutUpdateEventTimer.startOneShot(0); | |
502 } | |
503 | |
504 void RenderNamedFlowThread::dispatchRegionOversetChangeEvent() | |
505 { | |
506 RenderFlowThread::dispatchRegionOversetChangeEvent(); | |
507 InspectorInstrumentation::didChangeRegionOverset(&document(), m_namedFlow.ge
t()); | |
508 | |
509 if (!m_regionOversetChangeEventTimer.isActive() && m_namedFlow->hasEventList
eners()) | |
510 m_regionOversetChangeEventTimer.startOneShot(0); | |
511 } | |
512 | |
513 void RenderNamedFlowThread::regionLayoutUpdateEventTimerFired(Timer<RenderNamedF
lowThread>*) | |
514 { | |
515 ASSERT(m_namedFlow); | |
516 | |
517 m_namedFlow->dispatchRegionLayoutUpdateEvent(); | |
518 } | |
519 | |
520 void RenderNamedFlowThread::regionOversetChangeEventTimerFired(Timer<RenderNamed
FlowThread>*) | |
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 s
cript and may be reatached to one if the named flow is recreated in the styleshe
et. | |
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 logicalB
ottomForBox, LayoutUnit logicalTopForRegion, LayoutUnit logicalBottomForRegion) | |
562 { | |
563 bool regionIsEmpty = logicalBottomForRegion != LayoutUnit::max() && logicalT
opForRegion != LayoutUnit::min() | |
564 && (logicalBottomForRegion - logicalTopForRegion) <= 0; | |
565 return (logicalBottomForBox - logicalTopForBox) > 0 | |
566 && !regionIsEmpty | |
567 && logicalTopForBox < logicalBottomForRegion && logicalTopForRegion < lo
gicalBottomForBox; | |
568 } | |
569 | |
570 // Retrieve the next node to be visited while computing the ranges inside a regi
on. | |
571 static Node* nextNodeInsideContentNode(const Node& currNode, const Node* content
Node) | |
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, cons
t RenderRegion* region) const | |
581 { | |
582 LayoutUnit logicalTopForRegion; | |
583 LayoutUnit logicalBottomForRegion; | |
584 | |
585 // extend the first region top to contain everything up to its logical heigh
t | |
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(*n
ode, 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)->relativePositionLogi
calOffset()); | |
631 } | |
632 | |
633 LayoutUnit offsetTop = renderer->containingBlock()->offsetFromLogica
lTopOfFirstPage(); | |
634 const LayoutPoint logicalOffsetFromTop(isHorizontalWritingMode() ? L
ayoutUnit() : offsetTop, | |
635 isHorizontalWritingMode() ? offsetTop : LayoutUnit()); | |
636 | |
637 boundingBox.moveBy(logicalOffsetFromTop); | |
638 | |
639 LayoutUnit logicalTopForRenderer = region->logicalTopOfFlowThreadCon
tentRect(boundingBox); | |
640 LayoutUnit logicalBottomForRenderer = region->logicalBottomOfFlowThr
eadContentRect(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 th
e region, | |
644 // otherwise just move the start position after this node and keep s
kipping them until we found a proper start position. | |
645 if (!boxIntersectsRegion(logicalTopForRenderer, logicalBottomForRend
erer, 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 con
tained 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() < logicalTopForRegi
on) | |
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 positio
n to be before their children | |
677 startsAboveRegion = true; | |
678 range->setStart(Position(node, Position::PositionIsBeforeChi
ldren)); | |
679 } | |
680 } else { // node starts inside region | |
681 // for elements that start inside the region, set the start posi
tion 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 && (endsBelowR
egion || (!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()) < logicalBottomForRe
gion) { | |
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 positio
n to be after their children | |
711 range->setEnd(Position(node, Position::PositionIsAfterChildr
en)); | |
712 endsBelowRegion = true; | |
713 lastEndNode = node; | |
714 } | |
715 } else { // node ends inside region | |
716 // for elements that ends inside the region, set the end positio
n 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->isDescendantO
f(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 } | |
OLD | NEW |